#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include "PCB.h"
#include "r2.h"

pcb *ready_head = NULL;
pcb *ready_tail = NULL;
pcb *blocked_head = NULL;
pcb *blocked_tail = NULL;
pcb *suspended_ready_head = NULL;
pcb *suspended_ready_tail = NULL;
pcb *suspended_blocked_head = NULL;
pcb *suspended_blocked_tail = NULL;

//procedures for use on PCBs

/*Allocates a PCB using sys_alloc_mem, marks it as in use, 
 *and returns pcbPtr type that points to the PCB.
 *
 *Parameters: None
 *Returns: pcbPtr, a type that is equivalent to pcb *.  returns null if allocate fails
 */
pcb *allocate_PCB() {
	//tip - use sizeof with sys_alloc_mem to get correct number of bytes
	pcb *newpcb;
	printf("pcb is %d bytes\n", sizeof(pcb));
	newpcb =  (pcb*) sys_alloc_mem( sizeof(pcb));
		if(newpcb == NULL){
			printf("There was an error creating the PCB\n");
		}
		else{
			//strcpy( newpcb->name, "\0");
			newpcb->type = 0;
			newpcb-> priority = 0;
			newpcb-> state = 0;
			newpcb->suspended = 0;
			newpcb->stack_top = NULL;
			newpcb->stack_base = NULL;
			newpcb->mem_load_address = NULL;
			newpcb->mem_exec_address = NULL;
			newpcb->mem_size = 0;
			newpcb->prev = NULL;
			newpcb->next = NULL;
		}
		return newpcb;
	
}

/*Initializes PCB by calling allocate-PCB and then setting values based on
 *defaults and user input given in parameters
 *
 *Parameters: name, a char array holding the desired name
 *            type, an int giving the class (application or system)
 *            priority, an int giving process' desired priority
 *Returns: pcb *.  returns null if allocate fails or if invalid parameter
 */
pcb *setup_PCB(char name0[], int type0, int priority0) {
	//note: parameters and return values based on TA's lab slides for R2
	//(setup calls allocate), not R2 manual (allocate called then setup called)

	//check priority x for -128 <= x <= 127
	//check name for length less than PROC_NAME_LEN (or just use only that many chars)
	//also check check that name (or first PROC_NAME_LEN number of chars) is unique
	//check tyoe for either PROC_CLASS_APP or PROC_CLASS_SYS
	//use default values for remaining fields during initialization
	pcb *anew_pcb = NULL;
	printf(":%d:\n", PROC_NAME_LEN);
	if(priority0 >= -128 && priority0 <= 127){
		if(type0 == PROC_CLASS_APP || type0 == PROC_CLASS_SYS){
			if(strlen(name0) <= PROC_NAME_LEN){
				if(find_pcb(name0) == NULL){
					printf(":%s:%d\n", name0, strlen(name0));
				anew_pcb =  (pcb*) sys_alloc_mem( sizeof(pcb));
					//anew_pcb = (pcb *)allocate_PCB();
					strcpy(anew_pcb->name, name0);
					printf(":%s:\n", anew_pcb->name);
					anew_pcb->type = type0;
					anew_pcb->priority = priority0;
					anew_pcb->state = PROC_STATE_READY;
					anew_pcb->suspended = PROC_NOT_SUSP;
					//anew_pcb->next = NULL;
					//anew_pcb->prev = NULL;
					//new_pcb->stack_top = NULL;
					//new_pcb->stack_base = NULL;
					//new_pcb->mem_load_address = NULL;
					//new_pcb->mem_exec_address = NULL;
					//initialize stack area somehow? possibly all zero values?
				}
				else{
					printf("That name is not unique\n");
				}
			}
			else{
			printf("The name was too long\n");
			}
		}
		else{
		printf("That is not a valid class\n");
		}
	}
	else{
	printf("That is not a valid priority\n");
	}
	
	return anew_pcb;
}


pcb *find_pcb(char name[]){
	pcb *curr = NULL;
	//ready
	curr = traverse_find(ready_head, name);
	if (curr != NULL)
		return curr;
	//blocked
	curr = traverse_find(blocked_head, name);
	if (curr != NULL)
		return curr;
	//suspended-ready
	curr = traverse_find(suspended_ready_head, name);
	if (curr != NULL)
		return curr;
	//suspended-blocked
	curr = traverse_find(suspended_blocked_head, name);
	if (curr != NULL)
		return curr;
	//else if
	//running (just in case the new process has same name as running process, if possible?)
	//
	return NULL;
}

//helper function for find that compares the name of each element of queue
//returns null if name not in queue
pcb *traverse_find(pcb *head, char name[]) {
	pcb *curr = head;
	
	/*
	
	if (curr != NULL) {
		//queue not empty
		while (curr != NULL) {
			if (strcmp(curr->name, name) == 0){
				return curr;
			}
			curr = curr->next;
		}
	}
	return curr;
	
	*/
	
	while (curr != NULL){
	
		if (strcmp(curr->name, name) == 0){
				return curr;
		}
			curr = curr->next;
	}
	return NULL;
}

/*
*	This function is used to insert a PCB into its queue
*	Uses the traverse() function
*	Parameters: this_pcb: a pointer to a PCB
*				state and suspended: two ints that specify which queue we are putting it in
*	Returns: int codes to confirm it was done properly, or an error code
*/
int insert_pcb(pcb *this_pcb, int state, int suspended){

	//if state == PROC_STATE_READY
	//use ready_head, insert based on priority
	//if suspended, use suspended_ready_head
	//else if state == PROC_STATE_BLOCKED
	//
	//else WRONG
	
	pcb *lower_node = NULL;
	pcb *higher_node = NULL;

	if(state == PROC_STATE_READY){
		if(suspended == PROC_NOT_SUSP){
			this_pcb->state = state;
			this_pcb->suspended = suspended;
			if(ready_head == NULL){
				ready_head = this_pcb;
				ready_tail = this_pcb;
				this_pcb->prev = NULL;
				this_pcb->next = NULL;
			}
			else{
				//check if greater than head, insert at beginning if so
				if (this_pcb->priority > ready_head->priority) {
					ready_head->prev = this_pcb;
					this_pcb->next = ready_head;
					ready_head = this_pcb;
				}
				//check if less than or equal to tail, insert at end if so
				else if (this_pcb->priority <= ready_tail->priority) {
					ready_tail->next = this_pcb;
					this_pcb->prev = ready_tail;
					ready_tail = this_pcb;
				}
				//traverse to find node with lower priority (will be at least 2 nodes in queue)
				lower_node = traverse(ready_head, this_pcb->priority);
				//update pointers based on returned node
				higher_node = lower_node->prev;
				lower_node->prev = this_pcb;
				this_pcb->next = lower_node;
				higher_node->next = this_pcb;
				this_pcb->prev = higher_node;
			}
		}
		else if(suspended == PROC_SUSP){
			this_pcb->state = state;
			this_pcb->suspended = suspended;
			if(suspended_ready_head == NULL){
				suspended_ready_head = this_pcb;
				suspended_ready_tail = this_pcb;
				this_pcb->prev = NULL;
				this_pcb->next = NULL;
			}
			else{
				//check if greater than head, insert at beginning if so
				if (this_pcb->priority > suspended_ready_head->priority) {
					suspended_ready_head->prev = this_pcb;
					this_pcb->next = suspended_ready_head;
					suspended_ready_head = this_pcb;
				}
				//check if less than or equal to tail, insert at end if so
				else if (this_pcb->priority <= suspended_ready_tail->priority) {
					suspended_ready_tail->next = this_pcb;
					this_pcb->prev = suspended_ready_tail;
					suspended_ready_tail = this_pcb;
				}
				//traverse to find node with lower priority (will be at least 2 nodes in queue)
				lower_node = traverse(suspended_ready_head, this_pcb->priority);
				//update pointers based on returned node
				higher_node = lower_node->prev;
				lower_node->prev = this_pcb;
				this_pcb->next = lower_node;
				higher_node->next = this_pcb;
				this_pcb->prev = higher_node;
			}
		}
		else{//it is not a valid suspended value
			printf("That is not a valid value for suspended for the insert-pcb function\n");
		}
	}
	else if(state == PROC_STATE_BLOCKED){
		if(suspended == PROC_NOT_SUSP){
			this_pcb->state = state;
			this_pcb->suspended = suspended;
			if(blocked_head == NULL){
				blocked_head = this_pcb;
				blocked_tail = this_pcb;
				this_pcb->prev = NULL;
				this_pcb->next = NULL;
			}
			else{
				//check if greater than head, insert at beginning if so
				if (this_pcb->priority > blocked_head->priority) {
					blocked_head->prev = this_pcb;
					this_pcb->next = blocked_head;
					blocked_head = this_pcb;
				}
				//check if less than or equal to tail, insert at end if so
				else if (this_pcb->priority <= blocked_tail->priority) {
					blocked_tail->next = this_pcb;
					this_pcb->prev = blocked_tail;
					blocked_tail = this_pcb;
				}
				//traverse to find node with lower priority (will be at least 2 nodes in queue)
				lower_node = traverse(blocked_head, this_pcb->priority);
				//update pointers based on returned node
				higher_node = lower_node->prev;
				lower_node->prev = this_pcb;
				this_pcb->next = lower_node;
				higher_node->next = this_pcb;
				this_pcb->prev = higher_node;
			}	
		}
		else if(suspended == PROC_SUSP){
			this_pcb->state = state;
			this_pcb->suspended = suspended;
			if(suspended_blocked_head == NULL){
				suspended_blocked_head = this_pcb;
				suspended_blocked_tail = this_pcb;
				this_pcb->prev = NULL;
				this_pcb->next = NULL;
			}
			else{
				//check if greater than head, insert at beginning if so
				if (this_pcb->priority > suspended_blocked_head->priority) {
					suspended_blocked_head->prev = this_pcb;
					this_pcb->next = suspended_blocked_head;
					suspended_blocked_head = this_pcb;
				}
				//check if less than or equal to tail, insert at end if so
				else if (this_pcb->priority <= suspended_blocked_tail->priority) {
					suspended_blocked_tail->next = this_pcb;
					this_pcb->prev = suspended_blocked_tail;
					suspended_blocked_tail = this_pcb;
				}
				//traverse to find node with lower priority (will be at least 2 nodes in queue)
				lower_node = traverse(suspended_blocked_head, this_pcb->priority);
				//update pointers based on returned node
				higher_node = lower_node->prev;
				lower_node->prev = this_pcb;
				this_pcb->next = lower_node;
				higher_node->next = this_pcb;
				this_pcb->prev = higher_node;
			}
		}
		else{//it is not a valid suspended value
			printf("That is not a valid value for suspended for the insert-pcb function\n");
		}
	}
	else{//it is not a valid state
		printf("That is not a valid state for the insert-pcb function\n");
	}
}

/*
*	This function goes through a given queue and looks at each PCB for one with a lower priority
*	NOTE: does not check if head is null - error will result if so!
*	Parameters: *head: a pointer to the head of the necessary queue
*				priority: an int that is the priority from the PCB we are placing 
*	Returns: a pointer to the first element with a lower priority, the one that we will need to insert in front of	
*/
pcb *traverse(pcb *head, int priority){
	pcb *curr = head;
	while ((curr->next != NULL) && (curr->priority >= priority)) {
		curr = curr->next;
	}
	return curr;
}

/*
*	This function releases all memory held by a PCB, it should be removed from a queue with remove_pcb before it is freed
*	Parameters: *this_pcb: a pointer to the PCB to be removed
*	Returns: integer success or error codes
*	
*/
int free_pcb(pcb *this_pcb){
	int free_mem_error = -1;
	free_mem_error = sys_free_mem(this_pcb);
	if(free_mem_error != 0){
		printf("There was a problem freeing the memory\n");
	}
	return free_mem_error;
}

/*
*	This function removed a PCB from it queue and connects the queue around it
*	Paramerers: *this_pcb: a pointer to the PCB to be removed
*				*head: a pointer the the start of the queue the pcb is in
*	Returns: integer success or error codes
*/
int remove_pcb(pcb *this_pcb, pcb *head, pcb *tail){
	pcb *before = this_pcb->prev;
	pcb *after = this_pcb->next;
	if(this_pcb->next == NULL && this_pcb->prev == NULL){ //only pcb in queue
		head = NULL;
		tail = NULL;
	}
	else if(this_pcb->next == NULL){ //last pcb in queue
		tail = this_pcb->prev;
	}
	else if(this_pcb->prev == NULL){ //first pcb in queue
		head = this_pcb->next;
	}
	else{ // somewhere in the middle
		before->next = this_pcb->next;
		after->prev = this_pcb->prev;
	}
	
	this_pcb->prev = NULL; //release pointers within this pcb
	this_pcb->next = NULL;
	return 0;
}

/*
*	checks to see if a name is in any queue
*	Returns: 0 if not in any queue, 1 if in a queue
*	Uses: traverse find finction
*/
int check_name(char name[]){
	int in_one, in_two, in_three, in_four = 1;
	if(traverse_find(ready_head, name) == NULL){
		in_one = 0;
	}
	if(traverse_find(blocked_head, name) == NULL){
		in_two = 0;
	}
	if(traverse_find(suspended_ready_head, name) == NULL){
		in_three = 0;
	}
	if(traverse_find(suspended_blocked_head, name) == NULL){
		in_four = 0;
	}
	if(in_one == 0 && in_two == 0 && in_three == 0 && in_four == 0){
		return 0;
	}
	else{
		return 1;
	}
}